package com.xhj.ipersistent.config;

import com.xhj.ipersistent.mapping.MappedStatement;
import com.xhj.ipersistent.session.SqlSession;

import javax.sql.DataSource;
import java.lang.reflect.*;
import java.util.HashMap;
import java.util.Map;

/**
 * Configuration
 *
 * @author XJks
 * @description 全局配置类，存放核心配置文件解析出来的内容
 */
public class Configuration {
    // 数据源对象
    private DataSource dataSource;

    /**
     * TODO:
     *  存放所有mapper.xml中sql标签的解析内容，如<select>、<insert>、<update>、<delete>等标签的解析结果
     *  该集合的key的含义是statementId，格式:namespace.id，比如:com.xhj.ipersistent.dao.IUserDao.selectById
     *  该集合的value的含义是MappedStatement对象，存放每个标签解析出来的结果
     */
    private Map<String, MappedStatement> mappedStatementMap = new HashMap();

    public Configuration() {
    }

    public Map<String, MappedStatement> getMappedStatementMap() {
        return mappedStatementMap;
    }

    public MappedStatement getMappedStatement(String statementId) {
        return mappedStatementMap.get(statementId);
    }

    public void setMappedStatementMap(Map<String, MappedStatement> mappedStatementMap) {
        this.mappedStatementMap = mappedStatementMap;
    }

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public <T> T getMapper(Class<T> mapperClass, SqlSession sqlSession) {
        // 使用JDK动态代理生成基于接口的代理对象
        Object proxy = Proxy.newProxyInstance(Configuration.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            /**
             * 处理对代理实例的方法调用并返回结果。当在与其关联的代理实例上调用方法时，此方法将在调用处理器上被调用。
             *
             * @param proxy 被调用方法的代理实例
             *
             * @param method 与在代理实例上调用的接口方法对应的 {@code Method} 实例。
             *               {@code Method} 对象的声明类将是声明该方法的接口，该接口可能是代理接口的超接口，代理类通过继承该方法获得。
             *
             * @param args 包含在代理实例上方法调用时传递的参数值的对象数组，如果接口方法没有参数，则为 {@code null}。
             *             基本类型的参数会被包装为相应的包装类实例，如{@code java.lang.Integer} 或 {@code java.lang.Boolean}。
             *
             * @return 要从代理实例上的方法调用返回的值。如果接口方法的声明返回类型为基本类型，则此方法返回的值必须是相应基本类型包装类的实例；否则，必须是可赋值给声明返回类型的类型。
             *         如果此方法返回的值为 {@code null}，而接口方法的返回类型为基本类型，则在代理实例上调用方法时会抛出 {@code NullPointerException}。
             *         如果此方法返回的值与接口方法声明的返回类型不兼容，则在代理实例上调用方法时会抛出 {@code ClassCastException}。
             *
             * @throws Throwable 在代理实例上方法调用时要抛出的异常。异常类型必须可以赋值给接口方法 {@code throws} 子句中声明的任何异常类型，或未检查异常类型{@code java.lang.RuntimeException} 或 {@code java.lang.Error}。
             *                   如果此方法抛出的受检异常不能赋值给接口方法 {@code throws} 子句中声明的任何异常类型，则在代理实例上调用方法时会抛出包含该异常的{@link UndeclaredThrowableException}。
             *
             * @see UndeclaredThrowableException
             */
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /**
                 * TODO:
                 *  相关思路:
                 *      1.通过XXXMapper类的全限定名.方法名 作为statementId去mappedStatementMap集合中获取MappedStatement对象，然后使用sqlSession传入mappedStatement对象，调用CRUD方法。
                 *      2.调用CRUD方法时，并不确定调用sqlSession的哪个方法，可能是selectList，也可能是selectOne，还可能是update、insert、delete等方法，这里需要根据标签的类型来判断调用哪个方法。
                 *      3.如果是<select>标签，可能会调用sqlSession的selectList方法，也可能会调用selectOne方法，这里需要根据接口方法的返回值类型来判断，如果返回值是集合类型，则调用selectList方法，否则调用selectOne方法。
                 *      4.如果是<insert>、<update>、<delete>标签，则直接调用sqlSession的insert、update、delete方法即可。
                 */
                // 获取所调用方法的类名和方法名
                String className = method.getDeclaringClass().getName();
                String methodName = method.getName();
                // 根据类名和方法名组装statementId
                String statementId = className + "." + methodName;
                // 从mappedStatementMap集合中获取MappedStatement对象
                MappedStatement mappedStatement = getMappedStatement(statementId);
                // 获取标签的类型
                String sqlCommandType = mappedStatement.getSqlCommandType();
                switch (sqlCommandType) {
                    case "select":
                        // 判断返回值是否为集合类型
                        if (method.getReturnType().isAssignableFrom(java.util.List.class)) {
                            return sqlSession.selectList(statementId, args == null ? null : args[0]);
                        } else {
                            return sqlSession.selectOne(statementId, args == null ? null : args[0]);
                        }
                    case "update":
                        return sqlSession.update(statementId, args == null ? null : args[0]);
                    case "delete":
                        return sqlSession.delete(statementId, args == null ? null : args[0]);
                    case "insert":
                        return sqlSession.insert(statementId, args == null ? null : args[0]);
                    default:
                        throw new UnsupportedOperationException("未知的sqlCommandType: " + sqlCommandType);
                }
            }
        });
        return (T) proxy;
    }
}
